﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using MediaStreamer.Containers;
using MediaStreamer.Transformers;
using Org.Flowertwig.Media.Information;
using Org.Flowertwig.Media.Information.Services;
using Raven.Client.Indexes;

namespace MediaStreamer
{
    public class PageHandler
    {
        private const char _headLineSplitter = ':';
        private static readonly char[] _headLinesSplitter = new[] { '\r', '\n' };

        private static readonly string[] _indexes = new[]
                                                        {
                                                            "MenuItems/Index",
                                                            "MenuItems/Series/Index",
                                                            "MenuItems/Series/Titles/Index",
                                                            "MenuItems/Series/View/Index",
                                                            "MenuItems/Series/Season/View/Index",
                                                            "MenuItems/Series/Episode/View/Index",
                                                            "Series/Titles/Index",
                                                            "Series/Titles/Descending/Index",
                                                            "MenuItems/Movies/Index",
                                                            "Series/Genres/Index"
                                                        };


        public static void CleanUp()
        {
            Storage.AddIndex<MenuItem>("MenuItems/DeleteIndexes",
                                      new IndexDefinitionBuilder<MenuItem>
                                      {
                                          Map =
                                              myClasses =>
                                              from myClass in myClasses
                                              select new
                                              {
                                                  Text = myClass.Text,
                                                  Location = myClass.LocationId,
                                                  ElementId = myClass.ElementId
                                              }
                                      });



            Storage.DeleteAllItemsOfType<MenuItem>("MenuItems/DeleteIndexes");
            Storage.DeleteIndex("MenuItems/DeleteIndexes");
            Storage.DeleteIndex("MenuItems/Index");
            Storage.DeleteIndex("MenuItems/Series/Index");
            Storage.DeleteIndex("MenuItems/Series/View/Index");
            Storage.DeleteIndex("MenuItems/Series/Titles/Index");
            Storage.DeleteIndex("MenuItems/Series/Titles/Descending/Index");
            Storage.DeleteIndex("MenuItems/Movies/Index");
        }

        public static void EnsureIndexesAndStaticItems()
        {
            #region testing generic creation of indexes
            // NOTE: Locations has to be in lower case!
            //var locations = new string[] 
            //{
            //    "/",
            //    "/series",
            //    "/series/titles",
            //    "/series/mattias",
            //    "/movies",
            //};

            //foreach (var location in locations)
            //{
            //    if (string.IsNullOrEmpty(location))
            //        continue;

            //    if (!location.StartsWith("/"))
            //        continue;

            //    if (location.EndsWith("/"))
            //        continue;

            //var test = new IndexDefinitionBuilder<MenuItem>
            //               {
            //                   Map =
            //                       myClasses =>
            //                       myClasses.Where(myClass => myClass.LocationId == location1).Select(
            //                           myClass => new
            //                                          {
            //                                              Text = myClass.Text,
            //                                              Location = myClass.LocationId,
            //                                              ElementId = myClass.ElementId
            //                                          })
            //               }.ToIndexDefinition(new DocumentConvention());
            //Storage.AddIndex<MenuItem>(
            //    string.Format("MenuItems{0}Index", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(location)),
            //    test);

            //var location1 = location;
            //Storage.AddIndex<MenuItem>(string.Format("MenuItems{0}Index", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(location)),
            //                          new IndexDefinitionBuilder<MenuItem>
            //                          {
            //                              Map =
            //                                  myClasses =>
            //                                  myClasses.Where( myClass => myClass.LocationId == location1).Select(
            //                                      myClass => new
            //                                                     {
            //                                                         Text = myClass.Text,
            //                                                         Location = myClass.LocationId,
            //                                                         ElementId = myClass.ElementId
            //                                                     })
            //                          });
            //}

            //var _documentStore = Storage.DocumentStore;

            //_documentStore.DatabaseCommands.PutIndex("MenuItems/Index",
            //                          new IndexDefinitionBuilder<MenuItem>
            //                          {
            //                              Map =
            //                                  myClasses =>
            //                                  from myClass in myClasses
            //                                  where myClass.LocationId == "/"
            //                                  select new
            //                                  {
            //                                      myClass.Text,
            //                                      myClass.NavigateToLocation
            //                                  }
            //                          });

            #endregion
            //CleanUp();
            //Console.WriteLine("# Page Cleanup done.");
            //return;

            EnsureMenuItems();
            EnsureNavigation();

            //Storage.EnsureIndex<Media>("Media/HasInformationId",
            //                          new IndexDefinitionBuilder<Media>
            //                          {
            //                              Map =
            //                                  medias =>
            //                                  from media in medias
            //                                  where media.InformationId != null
            //                                  select new
            //                                  {
            //                                      Name = media.Name
            //                                  }
            //                          });


            Storage.EnsureIndex("MenuItems/Index",
                                      new IndexDefinitionBuilder<MenuItem>
                                      {
                                          Map =
                                              myClasses =>
                                              from myClass in myClasses
                                              where myClass.LocationId == "/"
                                              orderby myClass.SortIndex ascending
                                              select new
                                              {
                                                  Text = myClass.Text,
                                                  Location = myClass.LocationId,
                                                  ElementId = myClass.ElementId
                                              }
                                      });

            Storage.EnsureIndex("MenuItems/Series/Index",
                                      new IndexDefinitionBuilder<MenuItem>
                                      {
                                          Map =
                                              myClasses =>
                                              from myClass in myClasses
                                              where myClass.LocationId == "/series"
                                              orderby myClass.SortIndex ascending
                                              select new
                                              {
                                                  Text = myClass.Text,
                                                  Location = myClass.LocationId,
                                                  ElementId = myClass.ElementId
                                              }
                                      });

            Storage.EnsureIndex("MenuItems/Series/Titles/Index",
                                      new IndexDefinitionBuilder<MenuItem>
                                      {
                                          Map =
                                              myClasses =>
                                              from myClass in myClasses
                                              where myClass.LocationId == "/series/titles"
                                              orderby myClass.SortIndex ascending
                                              select new
                                              {
                                                  Text = myClass.Text,
                                                  Location = myClass.LocationId,
                                                  ElementId = myClass.ElementId
                                              }
                                      });

            Storage.EnsureIndex("MenuItems/Series/View/Index",
                                      new IndexDefinitionBuilder<MenuItem>
                                      {
                                          Map =
                                              myClasses =>
                                              from myClass in myClasses
                                              where myClass.LocationId == "/series/view"
                                              orderby myClass.SortIndex ascending
                                              select new
                                              {
                                                  Text = myClass.Text,
                                                  Location = myClass.LocationId,
                                                  ElementId = myClass.ElementId
                                              }
                                      });

            Storage.EnsureIndex("MenuItems/Series/Season/View/Index",
                                      new IndexDefinitionBuilder<MenuItem>
                                      {
                                          Map =
                                              myClasses =>
                                              from myClass in myClasses
                                              where myClass.LocationId == "/series/season/view"
                                              orderby myClass.SortIndex ascending
                                              select new
                                              {
                                                  Text = myClass.Text,
                                                  Location = myClass.LocationId,
                                                  ElementId = myClass.ElementId
                                              }
                                      });

            Storage.EnsureIndex("MenuItems/Series/Episode/View/Index",
                                      new IndexDefinitionBuilder<MenuItem>
                                      {
                                          Map =
                                              myClasses =>
                                              from myClass in myClasses
                                              where myClass.LocationId == "/series/episode/view"
                                              orderby myClass.SortIndex ascending
                                              select new
                                              {
                                                  Text = myClass.Text,
                                                  Location = myClass.LocationId,
                                                  ElementId = myClass.ElementId
                                              }
                                      });

            Storage.EnsureIndex("Series/Titles/Index",
                                      new IndexDefinitionBuilder<Serie>
                                      {
                                          Map =
                                              series =>
                                              from serie in series
                                              /*orderby serie.Name ascending*/
                                              select new
                                              {
                                                  Text = serie.Name,
                                                  NavigateToLocation = "/series/view/?id=" + serie.Id + "&type=serie",
                                                  Location = "/series/titles",
                                                  ElementId = "List"
                                              },
                                          TransformResults = (database, series) =>
                                              from serie in series
                                              orderby serie.Name ascending
                                              select new
                                              {
                                                  Text = serie.Name,
                                                  NavigateToLocation = "/series/view/?id=" + serie.Id + "&type=serie",
                                                  Location = "/series/titles",
                                                  ElementId = "List"
                                              }
                                      });

            Storage.EnsureIndex("Series/Titles/Descending/Index",
                                      new IndexDefinitionBuilder<Serie>
                                      {
                                          Map =
                                              series =>
                                              from serie in series
                                              select new
                                              {
                                                  Text = serie.Name,
                                                  NavigateToLocation = "/series/view/?id=" + serie.Id + "&type=serie",
                                                  Location = "/series/titles",
                                                  ElementId = "List"
                                              },
                                          TransformResults = (database, series) =>
                                              from serie in series
                                              orderby serie.Name descending
                                              select new
                                              {
                                                  Text = serie.Name,
                                                  NavigateToLocation = "/series/view/?id=" + serie.Id + "&type=serie",
                                                  Location = "/series/titles",
                                                  ElementId = "List"
                                              }
                                      });

            //var list = new List<Serie>();


            //IEnumerable<string> genres1 = list.SelectMany(s => s.Genres).Distinct();
            //foreach (var genre in genres1)
            //{

            //}

            Storage.EnsureIndex("MenuItems/Movies/Index",
                                      new IndexDefinitionBuilder<MenuItem>
                                      {
                                          Map =
                                              myClasses =>
                                              from myClass in myClasses
                                              where myClass.LocationId == "/movies"
                                              orderby myClass.SortIndex ascending
                                              select new
                                              {
                                                  Text = myClass.Text,
                                                  Location = myClass.LocationId,
                                                  ElementId = myClass.ElementId
                                              }
                                      });

            Storage.EnsureIndex("Series/Genres/Index",
                                      new IndexDefinitionBuilder<Serie, MenuItem>
                                      {
                                          Map =
                                              series =>
                                              from serie in series
                                              from genre in serie.Genres
                                              select new
                                              {
                                                  Text = genre
                                              },
                                          TransformResults = (database, genres) =>
                                              from genre in genres
                                              select new
                                              {
                                                  Text = genre.Text,
                                                  Location = "/series/genres",
                                                  ElementId = "List"
                                              }
                                      });

            //Storage.EnsureIndex<Serie, MenuItem>("MenuItems/Series/Genres/Index",
            //                          new IndexDefinitionBuilder<Serie, MenuItem>
            //                          {
            //                              Map =
            //                                  series =>
            //                                  from serie in series
            //                                  group serie by serie.Genres into g
            //                                  select new
            //                                  {
            //                                      Name = g.Key
            //                                  },
            //                              TransformResults = (database, genres) =>
            //                                  from genre in genres
            //                                  select new
            //                                  {
            //                                      Text = genre,
            //                                      Location = "/series/genres",
            //                                      ElementId = "List"
            //                                  }
            //                          });


        }

        private static void EnsureMenuItems()
        {
            EnsureTopmenu();
            EnsureSerieSubmenu();
        }

        private static void EnsureSerieSubmenu()
        {
            var browseByTitlesItem = new MenuItem
            {
                Text = "Browse by titles",
                ElementId = "SubMenu",
                NavigateToLocation = "/series/titles"
            };

            var pagesWithSerieSubMenu = new[]
                                       {
                                           "/series",
                                           "/series/titles",
                                           "/series/view",
                                           "/series/season/view"
                                       };

            foreach (var page in pagesWithSerieSubMenu)
            {
                browseByTitlesItem.LocationId = page;
                Storage.AddDocument(browseByTitlesItem);
            }
        }

        private static void EnsureTopmenu()
        {
            var movieItem = new MenuItem
                                {
                                    Text = "Movies",
                                    ElementId = "Menu",
                                    NavigateToLocation = "/movies"
                                };
            var serieItem = new MenuItem
                                {
                                    Text = "Series",
                                    ElementId = "Menu",
                                    NavigateToLocation = "/series"
                                };

            var pagesWithTopMenu = new[]
                                       {
                                           "/",
                                           "/series",
                                           "/movies",
                                           "/series/titles",
                                           "/series/view",
                                           "/series/season/view"
                                       };

            foreach (var page in pagesWithTopMenu)
            {
                movieItem.LocationId = page;
                serieItem.LocationId = page;
                Storage.AddDocument(movieItem);
                Storage.AddDocument(serieItem);
            }
        }

        private static void EnsureNavigation()
        {
            if (!Storage.HasIndex("MenuItems/Index"))
            {
                // Add /series/titles
                Storage.AddDocument(new Navigation
                                        {
                                            PageId = "/series/titles",
                                            IndexId = "Series/Titles/Index"
                                        });
            }
        }

        private static bool TryGetStaticMenuItems(string pageId, out List<MenuItem> menuItems)
        {
            menuItems = new List<MenuItem>();

            try
            {
                using (var session = Storage.DocumentStore.OpenSession())
                {
                    string id = pageId;
                    var tmp = session.Query<MenuItem>().Where(item => item.LocationId == id);
                    menuItems.AddRange(tmp);

                    return menuItems.Count > 0;
                }
            }
            catch (Exception)
            {
                // TODO: Add better exception handling here ;)
            }

            return false;
        }

        public static bool TryGetIndexesForPage(string pageId, out List<string> indexes)
        {
            indexes = new List<string>();

            try
            {
                using (var session = Storage.DocumentStore.OpenSession())
                {
                    string id = pageId;
                    var tmp = session.Query<Navigation>().Where(nav => nav.PageId == id);
                    // NOTE: This is stupid but we can not use Select here because of a bug in RavenDB. /MB
                    //var result = session.Query<Navigation>().Where(nav => nav.PageId == id).Select(nav => nav.IndexId).ToList();
                    //indexes.AddRange(result);
                    foreach (var navigation in tmp)
                    {
                        indexes.Add(navigation.IndexId);
                    }

                    return indexes.Count > 0;
                }
            }
            catch (Exception exception)
            {
                // TODO: Add better exception handling here ;)
            }

            return false;
        }

        public static ResponseInfo CreateResponse(RequestInfo requestInfo)
        {
            var resourceId = requestInfo.ResourceId.ToLower();
            var responseInfo = new ResponseInfo();

            List<string> indexes;
            List<MenuItem> menuItems;

            var items = new List<MenuItem>();
            if (TryGetStaticMenuItems(requestInfo.ResourceId, out menuItems))
            {
                items.AddRange(menuItems);
            }

            if (TryGetIndexesForPage(requestInfo.ResourceId, out indexes))
            {
                foreach (var indexName in indexes)
                {
                    var tmpItems = Storage.GetDocumentsForIndex<MenuItem>(indexName);
                    items.AddRange(tmpItems);
                }
            }

            var id = requestInfo.Query.Get("id");
            var type = requestInfo.Query.Get("type");
            if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(type))
            {
                try
                {
                    switch (type)
                    {
                        case "serie":
                            var transformer = new SerieTransformer();
                            items.AddRange(transformer.Transform(id, requestInfo));
                            break;
                    }
                }
                catch (Exception)
                {
                    // We are ignoring all types of errors here and returns 404 instead.
                }
            }

            if (items.Count > 0)
            {
                var template = TemplateHandler.PopulateTemplate(requestInfo, items);

                responseInfo.Content = template;
                return responseInfo;
            }
            else if (resourceId.StartsWith("/styles"))
            {
                responseInfo.Content = TemplateHandler.PopulateStyle(requestInfo);
                responseInfo.ContentType = "text/css;charset=utf-8";
                return responseInfo;
            }
            else if (resourceId.StartsWith("/images"))
            {
                responseInfo.Content = TemplateHandler.PopulateImage(requestInfo);
                responseInfo.ContentType = "image/jpeg;charset=utf-8";
                return responseInfo;
            }

            responseInfo.Content = Encoding.UTF8.GetBytes("<html><head><title>404</title></head><body>404</body></html>");
            responseInfo.StatusCode = 404;
            return responseInfo;
        }

        public static ResponseInfo HandleRequest(byte[] buffer, int length)
        {
            var data = Encoding.UTF8.GetString(buffer, 0, length);
            var requestInfo = ParseRequest(data);
            var responseInfo = PageHandler.CreateResponse(requestInfo);
            return responseInfo;
        }

        private static RequestInfo CreateErrorRequestInfo(int statusCode)
        {
            return new RequestInfo
            {
                ResourceId = string.Format("/error{0}", statusCode),
                UserAgent = "To be done"
            };
        }

        private static RequestInfo ParseRequest(string data)
        {
            var lines = data.Split(_headLinesSplitter, StringSplitOptions.RemoveEmptyEntries);

            if (lines.Length == 0)
                return CreateErrorRequestInfo(400);

            var resourceInfo = lines[0].Split(' ');
            if (resourceInfo.Length != 3)
                return CreateErrorRequestInfo(400);

            if (!string.Equals("GET", resourceInfo[0], StringComparison.CurrentCulture))
                return CreateErrorRequestInfo(400);

            var resource = resourceInfo[1];

            var resourceSplit = resource.Split('?');
            var resourceId = resourceSplit[0];
            string resourceQuery = string.Empty;
            if (resourceSplit.Length == 2)
            {
                resourceQuery = resourceSplit[1];
            }

            if (!Validator.IsValid(resourceId))
                return CreateErrorRequestInfo(404);

            if (!string.Equals(resourceId, "/"))
            {
                resourceId = resourceId.TrimEnd('/');
            }

            var query = ParseQuery(resourceQuery);

            var requestInfo = new RequestInfo
            {
                ResourceId = resourceId,
                Query = query
            };

            for (int i = 1; i < lines.Length; i++)
            {
                var line = lines[i];
                int splitPos = line.IndexOf(_headLineSplitter);
                if (splitPos <= 0)
                    continue;

                //var metaLine = lines[i].Split(_headLineSplitter);
                //if (metaLine.Length != 2)
                //    continue;

                var key = line.Substring(0, splitPos).ToLower();
                var value = line.Substring(splitPos + 1).Trim();

                switch (key)
                {
                    case "host":
                        break;
                    case "user-agent":
                        requestInfo.UserAgent = value;
                        break;
                    case "Cookie":
                        break;
                    case "accept-language":
                        break;
                }
            }


            //GET / HTTP/1.1
            //Host: 127.0.0.1
            //User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
            //Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
            //Accept-Language: en-gb,en;q=0.5
            //Accept-Encoding: gzip, deflate
            //Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
            //Keep-Alive: 115
            //Connection: keep-alive
            //Cookie: __utma=96992031.1085590086.1294780929.1294780929.1294780929.1; __utmz=96992031.1294780929.1.1.utmcsr=localhost:5879|utmccn=(referral)|utmcmd=referral|utmcct=/Default.aspx
            //Cache-Control: max-age=0

            return requestInfo;
        }

        private static NameValueCollection ParseQuery(string resourceQuery)
        {
            var collection = new NameValueCollection();

            var pairs = resourceQuery.Split('&');
            foreach (var pair in pairs)
            {
                var split = pair.Split('=');
                if (split.Length == 2)
                {
                    var key = split[0];
                    var value = split[1];
                    // id=pv+v/j8O2/bOqyLZIHGBmzdE29/3mex7
                    if (Validator.IsValid(key) && Validator.IsValidValue(value))
                    {
                        collection.Add(key, value);
                    }
                }
            }

            return collection;
        }

        public static void Reset()
        {
            Console.WriteLine("# Reset - PageHandler : Start");
            Storage.EnsureIndex("MenuItems/DeleteIndexes",
                                      new IndexDefinitionBuilder<MenuItem>
                                      {
                                          Map =
                                              myClasses =>
                                              from myClass in myClasses
                                              select new
                                              {
                                                  Id = myClass.Id
                                              }
                                      });
            Storage.DeleteAllItemsOfType<MenuItem>("MenuItems/DeleteIndexes");

            foreach (var index in _indexes)
            {
                Storage.DeleteIndex(index);
            }
            Console.WriteLine("# Reset - PageHandler : End");
        }
    }
}
